﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Infinity.Common.Extensions;
using NUnit.Framework.Constraints;

namespace Infinity.Common
{
	/// <summary>
	/// Static helper class for Result&lt;T&gt; class
	/// </summary>
	public static class Result
	{
		/// <summary>
		/// Success result with value
		/// </summary>
		public static Result<T> Success<T>(T value)
		{
			return Result<T>.Success(value);
		}

		/// <summary>
		/// Result is failed
		/// </summary>
		public static Result<T> Fail<T>(T value = default(T))
		{
			return Result<T>.Fail();
		}
	}

	public sealed class Result<T>
	{
		/// <summary>
		/// Default constructor
		/// </summary>
		private Result()
		{
			Value = default(T);
			IsSuccess = false;
		}

		/// <summary>
		/// Is it a success result
		/// </summary>
		public bool IsSuccess { get; protected set; }

		/// <summary>
		/// Is it a failed result
		/// </summary>
		public bool IsFailed
		{
			get { return !IsSuccess; }
		}

		/// <summary>
		/// ResultClassTest second
		/// </summary>
		public T Value { get; protected set; }

		/// <summary>
		/// It returns true if it equals to 'obj'.
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public override bool Equals(object obj)
		{
			var result = obj as Result<T>;

			if (result.IsNull())
				return false;

			return result.IsSuccess == IsSuccess &&
			       (result.Value.ToObject().IsNotNull() &&
			        result.Value.Equals(Value));
		}

		/// <summary>
		/// It represents failed first
		/// </summary>
		public static Result<T> Fail()
		{
			return Failed;
		}

		public static Result<T> Failed = new Result<T>
			                                 {
				                                 IsSuccess = false
			                                 };

		/// <summary>
		/// It represents successed first
		/// </summary>
		public static Result<T> Success(T value)
		{
			return new Result<T>
				       {
					       IsSuccess = true,
					       Value = value
				       };
		}

		public static implicit operator T(Result<T> value)
		{
			Ensure.NotNull("second", value);

			if (value.IsFailed)
				Error.ThrowException(string.Format("Cannot cast to {0}, because the result is failed!", typeof(T).FullName));

			return value.Value;
		}

		public static implicit operator Result<T>(T value)
		{
			return Success(value);
		}

		public static bool operator ==(Result<T> first, Result<T> second)
		{
			if (first.IsNull() && second.IsNull())
				return true;

			if (first.IsNull() || second.IsNull())
				return false;

			if (first.IsSuccess || second.IsSuccess)
			{
				if (first.IsSuccess != second.IsSuccess)
					return false;

				return (first.Value.ToObject().IsNotNull() && first.Value.Equals(second.Value)) ||
				       (second.Value.ToObject().IsNotNull() && second.Value.Equals(first.Value)) ||
				       (first.Value.ToObject().IsNull() && second.Value.ToObject().IsNull());
			}

			return true;
		}

		public static bool operator !=(Result<T> result, Result<T> value)
		{
			return !(result == value);
		}

		public static bool operator ==(Result<T> first, T value)
		{
			if (first.IsNull() || !first.IsSuccess)
				return false;

			return ((object)value == null && (object)first.Value == null) ||
			       ((object)first.Value != null && first.Value.Equals(value)) ||
			       ((object)value != null && value.Equals(first.Value));
		}

		public static bool operator !=(Result<T> result, T value)
		{
			return !(result == value);
		}
	}
}